/*
 * Copyright (c) 2001 Fabrice Bellard
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/**
 * @file
 * video decoding with libavcodec API example
 *
 */

#include <libavcodec/version.h>
#include "utils.h"

#if LIBAVCODEC_VERSION_MAJOR >= 58

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <unistd.h>
#include <pthread.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>


#define DEFAULT_WRITE_FRAMES 20
static int g_sw_decode_frames=0;
static int g_sw_max_write_frames=DEFAULT_WRITE_FRAMES;
static int g_sw_dump_yuv420 = 0;
static int g_sw_video_width = 0;
static int g_sw_video_height = 0;
static int g_sw_codecid = AV_CODEC_ID_NONE;

extern float get_cpu_usage(double time_period);

static void yuv420p_save(AVFrame *frame, char *filename)
{
    unsigned char *buf = frame->data[0];
    int wrap = frame->linesize[0];
    int width = frame->width;
    int height = frame->height;

    FILE *f;
    int i;

    f = fopen(filename, "wb");
    if (!f)
    {
        fprintf(stderr, "Could not open file %s.\n", filename);
        return;
    }

    for (i = 0; i < height; i++)
        fwrite(buf + i * wrap, 1, width, f);

    buf = frame->data[1];
    wrap = frame->linesize[1];
    // write u
    for (i = 0; i < height / 2; i++)
        fwrite(buf + i * wrap, 1, width / 2, f);

    // write v
    buf = frame->data[2];
    wrap = frame->linesize[2];
    // write u
    for (i = 0; i < height / 2; i++)
        fwrite(buf + i * wrap, 1, width / 2, f);
    fclose(f);
}

static void decode(AVCodecContext *dec_ctx, AVFrame *frame, AVPacket *pkt,
                   const char *outdir,char* short_name)
{
    char file_name[1024]={};
    int ret;

    ret = avcodec_send_packet(dec_ctx, pkt);
    if (ret < 0)
    {
        fprintf(stderr, "Error sending a packet for decoding\n");
        exit(1);
    }

    while (ret >= 0)
    {
        ret = avcodec_receive_frame(dec_ctx, frame);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            return;
        else if (ret < 0)
        {
            fprintf(stderr, "Error during decoding\n");
            exit(1);
        }
        g_sw_decode_frames = dec_ctx->frame_number;
        if (g_sw_dump_yuv420 && dec_ctx->frame_number <= g_sw_max_write_frames) {
          // printf("saving frame %3d\n", dec_ctx->frame_number);
          snprintf(file_name, sizeof(file_name), "%s/%s-%d.nv12", outdir, short_name,
                   dec_ctx->frame_number);
          yuv420p_save(frame, file_name);
        }
    }
}

int swdecode(astring filename, astring outdir, int dump_file_count)
{
    const AVCodec *codec;

    AVFrame *frame;

    int ret;
    AVPacket *packet;

    AVFormatContext *input_ctx = NULL;
    int video_stream;

    AVStream *video = NULL;
    AVCodecContext *decoder_ctx = NULL;

    char short_name[256] = {0};
    char *p;
    char *last_dot;

#ifdef _WIN32
    strcpy(short_name, (p = strrchr(filename, '\\')) ? p + 1 : filename);
#else
    strcpy(short_name, (p = strrchr(filename, '/')) ? p + 1 : filename);
#endif
    // printf("short_name1 is %s.\n", short_name);
    last_dot = strrchr(short_name, '.');
    if (last_dot != NULL)
        *last_dot = '\0';
        // printf("short_name2 is %s.\n", short_name);

    if (outdir && dump_file_count != 0) {
      g_sw_dump_yuv420 = 1;
      g_sw_max_write_frames = dump_file_count;
      printf("using input write numbers %d.\n", g_sw_max_write_frames);
    }

    packet = av_packet_alloc();
    if (!packet)
        exit(1);

    /* open the input file */
    if (avformat_open_input(&input_ctx, filename, NULL, NULL) != 0)
    {
        fprintf(stderr, "Cannot open input file '%s'\n", filename);
        return -1;
    }

    if (avformat_find_stream_info(input_ctx, NULL) < 0)
    {
        fprintf(stderr, "Cannot find input stream information.\n");
        return -1;
    }

    /* find the video stream information */
    ret = av_find_best_stream(input_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0);
    if (ret < 0)
    {
        fprintf(stderr, "Cannot find a video stream in the input file\n");
        return -1;
    }
    video_stream = ret;

    if (!(decoder_ctx = avcodec_alloc_context3(codec)))
        return AVERROR(ENOMEM);

    video = input_ctx->streams[video_stream];
    if (avcodec_parameters_to_context(decoder_ctx, video->codecpar) < 0)
        return -1;

    g_sw_codecid = video->codecpar->codec_id;
    g_sw_video_height = video->codecpar->height;
    g_sw_video_width = video->codecpar->width;
    /* open it */
    if (avcodec_open2(decoder_ctx, codec, NULL) < 0)
    {
        fprintf(stderr, "Could not open codec\n");
        exit(1);
    }

    frame = av_frame_alloc();
    if (!frame)
    {
        fprintf(stderr, "Could not allocate video frame\n");
        exit(1);
    }
    struct timeval time_start, time_end;
    gettimeofday(&time_start, NULL);

    struct timeval time_stamp=time_start;
    long frame_num=0;
#define FRAME_INFO_PERIOD 100
    while (ret >= 0)
    {
        if ((ret = av_read_frame(input_ctx, packet)) < 0)
            break;

        if (video_stream == packet->stream_index)
            decode(decoder_ctx, frame, packet, outdir,short_name);
        frame_num++;
        if (frame_num % FRAME_INFO_PERIOD == FRAME_INFO_PERIOD-1) {
          struct timeval time_point;
          gettimeofday(&time_point, NULL);
          long run_time =
              (time_point.tv_sec - time_stamp.tv_sec) * 1000 +
              (time_point.tv_usec - time_stamp.tv_usec) / 1000;
          printf("cpu   usage : %5.2f\n",get_cpu_usage(run_time / 1000.0));
          time_stamp = time_point;
        }
        av_packet_unref(packet);
    }

    /* flush the decoder */
    decode(decoder_ctx, frame, NULL, outdir,short_name);

    printf("\n*************print test result begin*************\n");

#ifdef _WIN32
    clock_t t2 = clock();
    runTime_second = (double)(t2 - t1) / CLOCKS_PER_SEC;
#else
    gettimeofday(&time_end, NULL);
    double run_time = (time_end.tv_sec - time_start.tv_sec) * 1000 + (double)(time_end.tv_usec - time_start.tv_usec) / 1000;
    double runtime_second = run_time / 1000;
#endif
    double fps = g_sw_decode_frames / runtime_second;

    //printf("software decode        : %s\n", "swdec");
    printf("using totaltime        : %f s\n", runtime_second);
    printf("codec                  : %s\n", avcodec_get_name(g_sw_codecid));
    printf("video size             : %dx%d\n", g_sw_video_width, g_sw_video_height);
    printf("decode frames          : %d\n", g_sw_decode_frames);
    printf("decode fps             : %5.2f\n", fps);
    //fprintf(stderr,"COUNT|%f|0|fps\n",fps);
    printf("\n*************print test result end*************\n");

    av_frame_free(&frame);
    av_packet_free(&packet);
    avcodec_free_context(&decoder_ctx);
    avformat_close_input(&input_ctx);

    return 0;
}
#endif
